/*
 * See the file "LICENSE" ({@link https://bitbucket.org/kuhlmann/sdp}) for the full license governing this code.
 */
package de.up.ling.irtg.codec;

import de.up.ling.irtg.algebra.graph.GraphEdge;
import de.up.ling.irtg.algebra.graph.GraphNode;
import de.up.ling.irtg.algebra.graph.SGraph;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.DefaultDirectedGraph;

/**
 * Read semantic dependency graphs from a file. Based on Marco Kuhlmann's SDP
 * toolkit ({@link https://bitbucket.org/kuhlmann/sdp}).
 *
 * @author Zeljko Agic <zagic@uni-potsdam.de>
 */
class SdpGraphReader extends LineNumberReader {

    /**
     * Create a graph reader, using the default input-buffer size.
     *
     * @param reader a Reader object to provide the underlying stream
     */
    public SdpGraphReader(Reader reader) {
        super(reader);
    }

    /**
     * Create a graph reader that reads from the specified file. The file will
     * be read using the default input-buffer size.
     *
     * @param file the file to read from
     * @throws FileNotFoundException if the specified file does not exist, is a
     * directory rather than a regular file, or for some other reason cannot be
     * opened for reading
     */
    public SdpGraphReader(File file) throws FileNotFoundException {
        this(new FileReader(file));
    }

    /**
     * Create a graph reader that reads from the specified file. The file will
     * be read using the default input-buffer size.
     *
     * @param fileName the name of the file to read from
     * @throws FileNotFoundException if the specified file does not exist, is a
     * directory rather than a regular file, or for some other reason cannot be
     * opened for reading
     */
    public SdpGraphReader(String fileName) throws FileNotFoundException {
        this(new File(fileName));
    }
    
     /**
     * Reads a single paragraph. A paragraph is a list of lines terminated by a
     * blank (empty) line, or the end of the stream.
     *
     * @return the lines of the paragraph read, or {@code null} if the end of
     * the stream has been reached
     * @throws IOException if an I/O error occurs
     */
    public List<String> readParagraph() throws IOException {
        String line = super.readLine();
        if (line == null) {
            return null;
        } else {
            if (line.isEmpty()) {
                return Collections.<String>emptyList();
            } else {
                List<String> lines = new LinkedList<>();
                do {
                    lines.add(line);
                } while ((line = super.readLine()) != null && !line.isEmpty());
                return lines;
            }
        }
    }

    /**
     * Reads a single SDP-formatted graph.
     *
     * @return the graph read, or {@code null} if the end of the stream has been
     * reached
     * @throws IOException if an I/O error occurs
     */
    public SGraph readGraphAsSdp() throws IOException {
        List<String> lines = readParagraph();

        if (lines == null) {
            return null;
        } else {
            // Every graph should contain at least one token.
            // Graph ID has a fixed format.
            assert lines.size() >= 2;
            assert lines.get(0).matches("#2[0-9]{7}$");

            // Create the graph and add the wall or root node.
            DirectedGraph<GraphNode, GraphEdge> graph = new DefaultDirectedGraph<>(GraphEdge.class);
            
            // id->node mapping.
            Map<Integer, GraphNode> nodeMap = new HashMap<>();
            
            // Wall node; maybe not needed here.
            graph.addVertex(new GraphNode("0", "0"));

            List<Integer> predicates = new ArrayList<>();

            for (String line : lines.subList(1, lines.size())) {
                String[] tokens = line.split("\t");

                // There should be at least six columns: ID, FORM, LEMMA, POS, TOP, PRED
                // Enforce valid values for the TOP column.
                // Enforce valid values for the PRED column.
                assert tokens.length >= 6;
                assert tokens[4].equals("+") || tokens[4].equals("-");
                assert tokens[5].equals("+") || tokens[5].equals("-");

                Integer id = Integer.valueOf(tokens[0]);
                String form = tokens[1];
                // String lemma = tokens[2];
                // String pos = tokens[3];
                // boolean isTop = tokens[4].equals("+");
                boolean isPred = tokens[5].equals("+");

                // Node node = new Node(id, form, lemma, pos, isTop, isPred);
                GraphNode node = new GraphNode(id.toString(), form);

                // Make sure the node ID and in-file ID match.
                assert node.getName().equals(tokens[0]);
                
                graph.addVertex(node);
                nodeMap.put(id, node);
                

                if (isPred) {
                    predicates.add(id);
                }
            }

            int id = 1;

            for (String line : lines.subList(1, lines.size())) {
                String[] tokens = line.split("\t");
                
                // There should be exactly 6 + number of predicates many columns.
                assert tokens.length == 6 + predicates.size();

                for (int i = 6; i < tokens.length; i++) {
                    if (!tokens[i].equals("_")) {
                        // Edge edge = new Edge(predicates.get(i - 6), id, tokens[i]);
                        // graph.addEdge(graph.getNodeById(predicates.get(i - 6)), graph.getNodeById(id), edge);
                        GraphEdge edge = new GraphEdge(nodeMap.get(predicates.get(i - 6)), nodeMap.get(id));
                        edge.setLabel(tokens[i]);
                        graph.addEdge(nodeMap.get(predicates.get(i - 6)), nodeMap.get(id), edge);
                    }
                }

                ++id;
            }

            // If a node is labeled as a predicate, it should have outgoing edges.
            // for (GraphNode node : graph.vertexSet()) {
            //     assert !node.isPred() || (graph.outDegreeOf(node) != 0);
            // }

            return new SGraph(graph);
        }
    }
    

}
